﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LLClient.ClientServiceRef;
using System.ServiceModel.Description;
using LLClient.ServerProxy;
using System.Collections.ObjectModel;
using System.ServiceModel;

namespace LLClient.ServerProxy
{
    public class Proxy : ServicePortalCallback
    {
        private ServicePortalClient _proxyService = null;

        MetadataExchangeClient mexClient = new MetadataExchangeClient(new Uri("http://localhost/LLWebService/ClientService.svc?wsdl"), MetadataExchangeClientMode.HttpGet);
        MetadataSet metadata = null;

        Client _currentClient = null;
        static Proxy _instance = null;

        #region Delegates and Events

        public event EventHandler ProxyClosed;
        public event EventHandler ProxyFaulted;

        public delegate void ProxyConnectEventHandler(object sender, ProxyConnectEventArgs e);
        public event ProxyConnectEventHandler ProxyConnect;

        public delegate void ProxyEventHandler(object sender, ProxyEventArgs e);
        public event ProxyEventHandler ProxyEvent;

        public delegate void ProxyCallbackEventHandler(object sender, ProxyCallbackEventArgs e);
        public event ProxyCallbackEventHandler ProxyCallbackEvent;

        public delegate void ProxyBattleCallbackEventHandler(object sender, ProxyCallbackBattleArgs e);
        public event ProxyBattleCallbackEventHandler ProxyBattleCallbackEvent;

        public delegate void ProxyStatisticCallbackEventHandler(object sender, ProxyStatisticCallbackArgs e);
        public event ProxyStatisticCallbackEventHandler ProxyStatisticCallbackEvent;

        #endregion

        public static Proxy Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new Proxy();
                }

                return _instance;
            }
        }

        public Client CurrentClient
        {
            get { return _currentClient; }
        }

        public ServicePortalClient ProxyService
        {
            get { return _proxyService; }
            set
            {
                _proxyService = value;
            }
        }

        static readonly object syncObj = new object();

        public static Proxy GetInstance()
        {
            lock (syncObj)
            {
                if (_instance == null)
                {

                    _instance = new Proxy();
                }

                return _instance;
            }
        }

        protected void OnProxyEvent(ProxyEventArgs e)
        {
            if (ProxyEvent != null)
            {
                ProxyEvent(this, e);
            }
        }

        protected void OnProxyCallbackEvent(ProxyCallbackEventArgs e)
        {
            if (ProxyCallbackEvent != null)
            {
                ProxyCallbackEvent(this, e);
            }
        }
        
        protected void OnProxyConnect(ProxyConnectEventArgs e)
        {
            if (ProxyConnect != null)
            {
                ProxyConnect(this, e);
            }
        }

        protected void OnProxyBattleCallbackEvent(ProxyCallbackBattleArgs e)
        {
            if (ProxyBattleCallbackEvent != null)
            {
                ProxyBattleCallbackEvent(this, e);
            }
        }

        protected void OnProxyStatisticCallbackEvent(ProxyStatisticCallbackArgs e)
        {
            if (ProxyStatisticCallbackEvent != null)
            {
                ProxyStatisticCallbackEvent(this, e);
            }
        }

        protected void OnProxyClosed(EventArgs e)
        {
            if (ProxyClosed != null)
            {
                ProxyClosed(this, e);
            }
        }

        protected void OnProxyFaulted(EventArgs e)
        {
            if (ProxyFaulted != null)
            {
                ProxyFaulted(this, e);
            }
        }

        
        /// <summary>
        /// Method to handle when there is a client who comes or leaves
        /// </summary>
        public void ConnectionHandler(Client sender, string room, CallbackType callbackType)
        {
            ProxyCallbackEventArgs e = new ProxyCallbackEventArgs();
            e.Sender = sender;
            e.Message = null;
            e.CallbackType = callbackType;

            this.OnProxyCallbackEvent(e);
        }
        
        public void RefreshClient(ObservableCollection<Client> clients)
        {
            ProxyEventArgs e = new ProxyEventArgs();
            e.Clients = clients;
            e.CallbackType = CallbackType.ClientRefresh;
            OnProxyEvent(e);
        }

        public void RefreshAvailableUser(ObservableCollection<Client> clients)
        {
            ProxyEventArgs e = new ProxyEventArgs();
            e.Clients = clients;
            e.CallbackType = CallbackType.AvailableUserRefresh;
            OnProxyEvent(e);
        }

        public void ReceiveMessage(Message message)
        {
            ProxyCallbackEventArgs e = new ProxyCallbackEventArgs();
            e.Sender = message.Sender;
            e.Message = message;
            e.CallbackType = CallbackType.ReceiveGlobalMessage;

            this.OnProxyCallbackEvent(e);
        }

        public void ReceiveWhisper(Message message)
        {
            ProxyCallbackEventArgs e = new ProxyCallbackEventArgs();
            e.Sender = message.Sender;
            e.Message = message;
            e.CallbackType = CallbackType.ReceiveWhisper;

            this.OnProxyCallbackEvent(e);
        }

        public void ReceiveNotifyClientConnect(Client joinedClient)
        {
            this.ConnectionHandler(joinedClient, null, CallbackType.ClientJoin);
        }

        public void ReceiveNotifyClientDisconnect(Client leaver)
        {
            ProxyCallbackEventArgs e = new ProxyCallbackEventArgs();
            e.Sender = leaver;
            e.CallbackType = CallbackType.ClientLeave;
            OnProxyCallbackEvent(e);
        }


        public void Connect(Client client)
        {
            if (this._proxyService == null)
            {
                this._proxyService = new ServicePortalClient(new InstanceContext(this));

                if (this.CheckService())
                {
                    this._proxyService.InnerDuplexChannel.Closed -= InnerDuplexChannel_Closed;
                    this._proxyService.InnerDuplexChannel.Faulted -= InnerDuplexChannel_Faulted;
                    this._proxyService.InnerDuplexChannel.Closed += new EventHandler(InnerDuplexChannel_Closed);
                    this._proxyService.InnerDuplexChannel.Faulted += new EventHandler(InnerDuplexChannel_Faulted);

                    this._currentClient = client;
                    
                    IAsyncResult iar = this._proxyService.BeginConnect(this._currentClient, new AsyncCallback(OnEndConnect), null);
                }
                else
                {
                    this._proxyService = null;
                    ProxyConnectEventArgs e = new ProxyConnectEventArgs();
                    e.Connected = false;
                    e.Message = "Unable to connect to server. Maybe the server is down";
                    OnProxyConnect(e);
                }
            }
        }

        void InnerDuplexChannel_Closed(object sender, EventArgs e)
        {
            if (this.ProxyService != null)
            {
                if (this.ProxyService.State == CommunicationState.Opened)
                {
                    this.ProxyService.Close();
                    this.ProxyService = null;
                }
            }
        }

        void InnerDuplexChannel_Faulted(object sender, EventArgs e)
        {
            if (this._proxyService != null)
            {
                if (this._proxyService.State == CommunicationState.Faulted)
                {
                    this._proxyService.Abort();
                    this._proxyService = null;
                    this.OnProxyFaulted(e);
                }
            }
        }

        public void CloseSession()
        {
            EventArgs e = new EventArgs();

            try
            {
                if (this.ProxyService != null)
                {
                    this.ProxyService.DisconnectAsync(this._currentClient);
                }
            }
            catch { }
            finally
            {
                if (this.ProxyService != null)
                {
                    try
                    {
                        if (this.ProxyService.State == CommunicationState.Opened)
                        {
                            this.ProxyService.Close();
                        }
                        else if (this.ProxyService.State == CommunicationState.Faulted)
                        {
                            this.ProxyService.Abort();
                        }
                    }
                    catch { }
                    finally
                    {
                        this.ProxyService = null;

                        OnProxyClosed(e);
                    }
                }
            }

            this.ClearEvent();
        }

        private void OnEndConnect(IAsyncResult iar)
        {
            try
            {
                ProxyConnectEventArgs e = new ProxyConnectEventArgs();
                bool connected = this.ProxyService.EndConnect(iar);

                e.Connected = connected;
                OnProxyConnect(e);
                
                if (!connected)
                {
                    this.CloseSession();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        public bool CheckService()
        {
            bool isServiceUp = true;

            try
            {
                metadata = mexClient.GetMetadata();
            }
            catch (Exception ex)
            {
                isServiceUp = false;
            }

            return isServiceUp;
        }

        private void ClearEvent()
        {
            this.ProxyCallbackEvent = null;
            this.ProxyClosed = null;
        }

        /// <summary>
        /// Method to create new user
        /// </summary>
        public int SignUp(User user, Captain captain)
        {
            int result = -1;

            if (this._proxyService == null)
            {
                this._proxyService = new ServicePortalClient(new InstanceContext(this));

                if (this.CheckService())
                {
                    result = _proxyService.SignUp(user, captain);
                }
                else
                {
                    this._proxyService = null;
                    ProxyConnectEventArgs e = new ProxyConnectEventArgs();
                    e.Connected = false;
                    e.Message = "Unable to connect to server. Maybe the server is down";
                    OnProxyConnect(e);
                }
            }

            return result;
        }


        // for testing only
        public int AddCaptain(string captain_name)
        {
            int result = -1;

            if (this._proxyService == null)
            {
                this._proxyService = new ServicePortalClient(new InstanceContext(this));

                if (this.CheckService())
                {
                    result = _proxyService.AddCaptain(captain_name);
                }
                else
                {
                    this._proxyService = null;
                    ProxyConnectEventArgs e = new ProxyConnectEventArgs();
                    e.Connected = false;
                    e.Message = "Unable to connect to server. Maybe the server is down";
                    OnProxyConnect(e);
                }
            }

            return result;
        }
        
        public void NotifySendChallange(bool isSent)
        {
            ProxyCallbackEventArgs e = new ProxyCallbackEventArgs();
            e.Sender = this._currentClient;
            e.Message = new Message();
            e.Message.Respond = isSent;
            e.CallbackType = CallbackType.ReceiveSendChallange;

            this.OnProxyCallbackEvent(e);
        }
        public void NotifyReceiveChallange(Client challanger)
        {
            ProxyCallbackEventArgs e = new ProxyCallbackEventArgs();
            e.Sender = challanger;
            e.Message = new Message();
            e.Message.Sender = challanger;
            e.CallbackType = CallbackType.ReceiveChallange;

            this.OnProxyCallbackEvent(e);
        }

        public void NotifyChallangeRespond(Client sender, bool respond)
        {
            ProxyCallbackEventArgs e = new ProxyCallbackEventArgs();
            e.Sender = sender;
            e.Message = new Message();
            e.Message.Sender = sender;
            e.Message.Respond = respond;
            e.CallbackType = CallbackType.ReceiveRespondChallange;

            this.OnProxyCallbackEvent(e);
        }

        public void NotifyEnemyClientAndMe(Client enemy, Client you, Dictionary<int, int> enemyShips, Dictionary<int, int> yourShips)
        {
            ProxyCallbackEventArgs e = new ProxyCallbackEventArgs();
            e.Sender = you;
            e.Message = new Message();
            e.Message.Sender = enemy;
            e.Player1Ships = yourShips;
            e.Player2Ships = enemyShips;
            e.CallbackType = CallbackType.ReceiveEnemyClient;

            this.OnProxyCallbackEvent(e);
        }

        // later
        public void NotifyStatistic(ObservableCollection<BattleReport> battleStats, ObservableCollection<CptStats> captainStats, ObservableCollection<FractionStats> fractionStats, ObservableCollection<Captain> captains)
        {
            ProxyStatisticCallbackArgs e = new ProxyStatisticCallbackArgs();
            
            e.CallbackType = CallbackType.ReceiveStatistic;
            e.BattleReport = battleStats.ToList<BattleReport>();
            e.CaptainStats = captainStats.ToList<CptStats>();
            e.FractionStats = fractionStats.ToList<FractionStats>();
            e.CaptainList = captains.ToList<Captain>();

            OnProxyStatisticCallbackEvent(e);
        }

        #region Battle Interface

        public void NotifyBattleCreation(Message message)
        {
            ProxyCallbackEventArgs e = new ProxyCallbackEventArgs();
            e.Sender = CurrentClient;
            e.Message = message;
            e.CallbackType = CallbackType.BattleCreation;

            OnProxyCallbackEvent(e);
        }

        public void NotifyPlacementDone(Client sender)
        {
            ProxyCallbackEventArgs e = new ProxyCallbackEventArgs();
            e.Sender = sender;
            e.Message = new Message();
            e.CallbackType = CallbackType.PlacementDone;
            e.Message.Sender = sender;

            OnProxyCallbackEvent(e);
        }

        public void NotifyPlayerReady(Client sender)
        {
            ProxyCallbackEventArgs e = new ProxyCallbackEventArgs();
            e.Sender = sender;
            e.Message = new Message();
            e.CallbackType = CallbackType.PlayerReady;
            e.Message.Sender = sender;

            OnProxyCallbackEvent(e);
        }

        public void ReceiveGameStarted(Message message)
        {
            ProxyCallbackEventArgs e = new ProxyCallbackEventArgs();
            e.Sender = message.Sender;
            e.Message = message;
            e.CallbackType = CallbackType.GameStarted;

            OnProxyCallbackEvent(e);
        }

        public void NotifyPauseBattle(Client sender, Turn turn)
        {
            ProxyCallbackBattleArgs e = new ProxyCallbackBattleArgs();
            e.Sender = sender;
            e.Turn = turn;
            e.CallbackType = CallbackType.PauseBattle;

            OnProxyBattleCallbackEvent(e);
        }

        public void NotifyResumeBattle(Client sender, Turn turn)
        {
            ProxyCallbackBattleArgs e = new ProxyCallbackBattleArgs();
            e.Sender = sender;
            e.Turn = turn;
            e.CallbackType = CallbackType.ResumeBattle;

            OnProxyBattleCallbackEvent(e);
        }

        public void ReceiveShotResult(Message message)
        {
            ProxyCallbackEventArgs e = new ProxyCallbackEventArgs();
            e.Sender = message.Sender;
            e.Message = message;
            e.CallbackType = CallbackType.ShotResult;

            OnProxyCallbackEvent(e);
        }

        public void NotifyGotShot(Client sender, Turn turn)
        {
            ProxyCallbackBattleArgs e = new ProxyCallbackBattleArgs();
            e.Sender = sender;
            e.Turn = turn;
            e.CallbackType = CallbackType.GotShot;

            OnProxyBattleCallbackEvent(e);
        }

        public void NotifyBattleOver(Message message)
        {
            ProxyCallbackEventArgs e = new ProxyCallbackEventArgs();
            e.Sender = message.Sender;
            e.Message = message;
            e.CallbackType = CallbackType.BattleOver;

            OnProxyCallbackEvent(e);
        }

        #endregion

        #region Asynchronus Delegates

        public IAsyncResult BeginRefreshClient(ObservableCollection<Client> clients, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndRefreshClient(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        public IAsyncResult BeginReceiveMessage(Message message, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndReceiveMessage(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        public IAsyncResult BeginReceiveWhisper(Message message, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndReceiveWhisper(IAsyncResult result)
        {
            throw new NotImplementedException();
        }
        
        public IAsyncResult BeginReceiveNotifyClientConnect(Client joinedClient, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndReceiveNotifyClientConnect(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        public IAsyncResult BeginReceiveNotifyClientDisconnect(Client leaver, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndReceiveNotifyClientDisconnect(IAsyncResult result)
        {
            throw new NotImplementedException();
        }
        public IAsyncResult BeginNotifySendChallange(bool isSent, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndNotifySendChallange(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        public IAsyncResult BeginNotifyReceiveChallange(Client challanger, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndNotifyReceiveChallange(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        public IAsyncResult BeginNotifyChallangeRespond(Client sender, bool respond, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndNotifyChallangeRespond(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        public IAsyncResult BeginRefreshAvailableUser(ObservableCollection<Client> clients, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndRefreshAvailableUser(IAsyncResult result)
        {
            throw new NotImplementedException();
        }
        public IAsyncResult BeginNotifyBattleCreation(Message message, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndNotifyBattleCreation(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        public IAsyncResult BeginNotifyPlacementDone(Client sender, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndNotifyPlacementDone(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        public IAsyncResult BeginNotifyPlayerReady(Client sender, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndNotifyPlayerReady(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        public IAsyncResult BeginNotifyPauseBattle(Client sender, Turn turn, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndNotifyPauseBattle(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        public IAsyncResult BeginNotifyResumeBattle(Client sender, Turn turn, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndNotifyResumeBattle(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        public IAsyncResult BeginReceiveShotResult(Message message, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndReceiveShotResult(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        public IAsyncResult BeginNotifyGotShot(Client sender, Turn turn, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndNotifyGotShot(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        public IAsyncResult BeginNotifyBattleOver(Message message, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndNotifyBattleOver(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        public IAsyncResult BeginReceiveGameStarted(Message message, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndReceiveGameStarted(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        public IAsyncResult BeginNotifyStatistic(ObservableCollection<BattleReport> battleStats, ObservableCollection<CptStats> captainStats, ObservableCollection<FractionStats> fractionStats, ObservableCollection<Captain> captains, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndNotifyStatistic(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        public IAsyncResult BeginNotifyEnemyClientAndMe(Client enemy, Client you, Dictionary<int, int> enemyShips, Dictionary<int, int> yourShips, AsyncCallback callback, object asyncState)
        {
            throw new NotImplementedException();
        }

        public void EndNotifyEnemyClientAndMe(IAsyncResult result)
        {
            throw new NotImplementedException();
        }

        #endregion

    }
}
